﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FixedStepDispatcherTimer.cs" company="TomCorp">
//   Thomas Heuberger
// </copyright>
// <summary>
//   Implementation of a real rime clock
//   downloaded from http://stackoverflow.com/questions/1329900/net-event-every-minute-on-the-minute-is-a-timer-the-best-option
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace FHIntranetParser {
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Windows.Threading;

    /// <summary>
    /// Implementation of a real rime clock
    /// downloaded from http://stackoverflow.com/questions/1329900/net-event-every-minute-on-the-minute-is-a-timer-the-best-option
    /// </summary>
    [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
    public class FixedStepDispatcherTimer {
        /// <summary>
        /// The step.
        /// </summary>
        private long step, nextTick, n;

        /// <summary>
        /// The timer.
        /// </summary>
        private DispatcherTimer timer;

        /// <summary>
        /// Initializes a new instance of the <see cref="FixedStepDispatcherTimer"/> class.
        /// </summary>
        /// <param name="interval">
        /// The interval.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown when the argument is out of range
        /// </exception>
        public FixedStepDispatcherTimer(TimeSpan interval) {
            if (interval < TimeSpan.Zero) {
                throw new ArgumentOutOfRangeException("interval");
            }

            this.timer = new DispatcherTimer();
            this.timer.Tick += this.OnTimerTick;
            this.step = interval.Ticks;
        }

        /// <summary>
        /// Occurs when the timer interval has elapsed.
        /// </summary>
        public event EventHandler Tick;

        /// <summary>
        /// Gets a value indicating whether is running.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:ElementMustNotBeOnSingleLine", Justification = "Reviewed. Suppression is OK here.")]
        public bool IsRunning { get { return this.timer.IsEnabled; } }
        
        /// <summary>
        /// Gets the elapsed.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1502:ElementMustNotBeOnSingleLine", Justification = "Reviewed. Suppression is OK here.")]
        public TimeSpan Elapsed { get { return new TimeSpan(this.n * this.step); } }

        /// <summary>
        /// The reset.
        /// </summary>
        public void Reset() {
            this.n = 0;
            this.nextTick = 0;
        }

        /// <summary>
        /// The start.
        /// </summary>
        public void Start() {
            var now = DateTime.Now.Ticks;
            this.nextTick = now + (this.step - (this.nextTick % this.step));
            this.timer.Interval = this.GetTimerInterval();
            this.timer.Start();
        }

        /// <summary>
        /// The stop.
        /// </summary>
        public void Stop() {
            this.timer.Stop();
            this.nextTick = DateTime.Now.Ticks % this.step;
        }

        /// <summary>
        /// The get timer interval.
        /// </summary>
        /// <returns>
        /// The <see cref="TimeSpan"/>.
        /// </returns>
        private TimeSpan GetTimerInterval() {
            var interval = this.nextTick - DateTime.Now.Ticks;
            if (interval > 0) {
                return new TimeSpan(interval);
            }

            return TimeSpan.Zero; // yield
        }

        /// <summary>
        /// The on timer tick.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void OnTimerTick(object sender, EventArgs e) {
            if (DateTime.Now.Ticks >= this.nextTick) {
                this.n++;
                if (this.Tick != null) {
                    this.Tick(this, EventArgs.Empty);
                }

                this.nextTick += this.step;
            }

            var interval = this.GetTimerInterval();
            Trace.WriteLine(interval);
            this.timer.Interval = interval;
        }
    }
}
